Hands-on_Ex03

Author

Teo Wee Siang Roy

Published

April 29, 2025

Modified

May 1, 2025

3 Programming Interactive Data Visualisation with R

pacman::p_load(ggiraph, plotly,patchwork, DT, tidyverse) 

Importing the data

exam_data <- read_csv("chap03/data/Exam_data.csv")
p <- ggplot(data=exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(tooltip = ID),
    stackgroups = TRUE, 
    binwidth = 1, 
    method = "histodot") +
  scale_y_continuous(NULL, 
                     breaks = NULL)
girafe(
  ggobj = p,
  width_svg = 6,
  height_svg = 6*0.618
)

Enhancing Dot Interactivity for Clearer Hover Feedback

To make it easier for users to identify which dot they are hovering over, we enhance the dot’s appearance by changing its size and color during hover.

exam_data$tooltip <- c(paste0(     
  "Name = ", exam_data$ID,         
  "\n Class = ", exam_data$CLASS)) 
theme_set(theme_minimal(base_size = 14, base_family = "Arial"))

p <- ggplot(data = exam_data, aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(tooltip = tooltip, data_id = ID),
    stackgroups = TRUE,
    binwidth = 1,
    method = "histodot",
    fill = "#2c7fb8",        # Soft blue fill
    color = "white",         # Dot border
    alpha = 0.85
  ) +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(
    title = "Distribution of Maths Scores",
    subtitle = "Each dot represents a student. Hover to see details.",
    caption = "Source: Exam_data.csv",
    x = "Maths Score",
    y = NULL
  ) +
  theme(
    plot.title = element_text(size = 18, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 13, hjust = 0.5),
    axis.text.x = element_text(size = 12),
    panel.grid.major.y = element_blank()
  )


options(ggiraph.tooltip_extra_css = "pointer-events: none;")

# Create the interactive widget
girafe(
  ggobj = p,
  width_svg = 8,
  height_svg = 8 * 0.618,
  options = list(
    opts_tooltip(css = "color: red; font-weight: bold; background-color: #f9f9f9;
                         border: 1px solid #ccc; padding: 6px 10px;
                         border-radius: 6px; box-shadow: 2px 2px 5px rgba(0,0,0,0.1);"),
    opts_hover(css = "fill:#f03b20;stroke:white;stroke-width:1.5px;")
  )
)

3.6.2 Displaying statistics on tooltip

tooltip <- function(y, ymax, accuracy = .01) {
  mean <- scales::number(y, accuracy = accuracy)
  sem <- scales::number(ymax - y, accuracy = accuracy)
  paste("Mean maths scores:", mean, "+/-", sem)
}

gg_point <- ggplot(data=exam_data, 
                   aes(x = RACE),
) +
  stat_summary(aes(y = MATHS, 
                   tooltip = after_stat(  
                     tooltip(y, ymax))),  
    fun.data = "mean_se", 
    geom = GeomInteractiveCol,  
    fill = "light blue"
  ) +
  stat_summary(aes(y = MATHS),
    fun.data = mean_se,
    geom = "errorbar", width = 0.2, size = 0.2
  )

girafe(ggobj = gg_point,
       width_svg = 8,
       height_svg = 8*0.618)

How to improve

exam_data <- exam_data %>%
  mutate(RACE = fct_reorder(RACE, MATHS, .fun = mean, .desc = FALSE))

tooltip <- function(y, ymax, accuracy = .01) {
  mean <- scales::number(y, accuracy = accuracy)
  sem <- scales::number(ymax - y, accuracy = accuracy)
  paste0("Mean Maths Score: ", mean, "\n± Standard Error: ", sem)
}

gg_point <- ggplot(data = exam_data, aes(x = RACE)) +
  stat_summary(
    aes(y = MATHS, tooltip = after_stat(tooltip(y, ymax))),
    fun.data = "mean_se",
    geom = GeomInteractiveCol,
    width = 0.5,
    fill = "lightblue"
  ) +
  stat_summary(
    aes(y = MATHS),
    fun.data = mean_se,
    geom = "errorbar",
    width = 0.2,
    size = 0.2
  ) +
  scale_y_continuous(breaks = seq(0, 100, by = 10)) +
  labs(
    title = "Average Maths Scores by Race",
    x = "Race",
    y = "Maths Score"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, size = 18, face = "bold"),
    axis.text.x = element_text(angle = 45, hjust = 1),
    axis.title.x = element_text(size = 14),
    axis.title.y = element_text(size = 14)
  )

girafe(
  ggobj = gg_point,
  width_svg = 8,
  height_svg = 8 * 0.618,
  options = list(
    opts_hover(css = "fill:orange;stroke:black;stroke-width:1.5px;"),
    opts_tooltip(css = "background-color:white; color:black; border:1px solid black; padding:5px; border-radius:5px;")
  )
)

3.6.5 Combining tooltip and hover effect

p <- ggplot(data=exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(              
    aes(tooltip = CLASS, 
        data_id = CLASS),              
    stackgroups = TRUE,                  
    binwidth = 1,                        
    method = "histodot") +               
  scale_y_continuous(NULL,               
                     breaks = NULL)
girafe(                                  
  ggobj = p,                             
  width_svg = 6,                         
  height_svg = 6*0.618,
  options = list(                        
    opts_hover(css = "fill: #202020;"),  
    opts_hover_inv(css = "opacity:0.2;") 
  )                                        
)                                        

Improve to show additional stats of the class scores

exam_data <- read_csv("chap03/data/Exam_data.csv")
class_summary <- exam_data %>%
  group_by(CLASS) %>%
  summarise(
    mean_score = round(mean(MATHS), 2),
    min_score = min(MATHS, na.rm = TRUE),
    max_score = max(MATHS, na.rm = TRUE),
    count = n(),
    .groups = "drop"
)

# Join the stats back to main data and add tooltip content
exam_data <- exam_data %>%
  left_join(class_summary, by = "CLASS") %>%
  mutate(tooltip_text = paste0(
    "Class: ", CLASS, "\n",
    "Student Score: ", MATHS, "\n",
    "Class Mean: ", mean_score, "\n",
    "Min Score: ", min_score, "\n",
    "Max Score: ", max_score, "\n",
    "Count: ", count
  ))

# Dotplot with enhanced tooltip
p <- ggplot(data = exam_data, aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(tooltip = tooltip_text, data_id = CLASS),
    stackgroups = TRUE,
    binwidth = 1,
    method = "histodot",
    fill = "#1f77b4",
    color = "white",
    alpha = 0.85
  ) +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(
    title = "Distribution of Maths Scores",
    x = "Maths Score"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 18),
    axis.text.x = element_text(size = 12)
  )

# Interactive rendering
girafe(
  ggobj = p,
  width_svg = 6,
  height_svg = 6 * 0.618,
  options = list(
    opts_hover(css = "fill: #202020; stroke: white; stroke-width: 1.2px;"),
    opts_hover_inv(css = "opacity: 0.15;"),
    opts_tooltip(css = "background-color: white; color: black; border: 1px solid #ccc; padding: 5px; border-radius: 4px; font-size: 12px;")
  )
)

##3.6.7 Coordinated Multiple Views with ggiraph

p1 <- ggplot(data=exam_data, 
       aes(x = MATHS)) +
  geom_dotplot_interactive(              
    aes(data_id = ID),              
    stackgroups = TRUE,                  
    binwidth = 1,                        
    method = "histodot") +  
  coord_cartesian(xlim=c(0,100)) + 
  scale_y_continuous(NULL,               
                     breaks = NULL)

p2 <- ggplot(data=exam_data, 
       aes(x = ENGLISH)) +
  geom_dotplot_interactive(              
    aes(data_id = ID),              
    stackgroups = TRUE,                  
    binwidth = 1,                        
    method = "histodot") + 
  coord_cartesian(xlim=c(0,100)) + 
  scale_y_continuous(NULL,               
                     breaks = NULL)

girafe(code = print(p1 + p2), 
       width_svg = 6,
       height_svg = 3,
       options = list(
         opts_hover(css = "fill: #202020;"),
         opts_hover_inv(css = "opacity:0.2;")
         )
       ) 

try out how to display all 3 subjects

exam_data <- exam_data %>%
  mutate(
    total_score = MATHS + ENGLISH + SCIENCE,
    tooltip_text = paste0(
      "Maths: ", MATHS, "\n",
      "English: ", ENGLISH, "\n",
      "Science: ", SCIENCE, "\n",
      "Total: ", total_score, " / 300"
    )
  )

# Step 2: Maths Plot
p1 <- ggplot(data = exam_data, aes(x = MATHS)) +
  geom_dotplot_interactive(
    aes(data_id = ID, tooltip = tooltip_text),
    stackgroups = TRUE,
    binwidth = 1,
    method = "histodot",
    fill = "#1f77b4",
    color = "white",
    alpha = 0.85
  ) +
  coord_cartesian(xlim = c(0, 100)) +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(title = "Maths Score", x = "MATHS") +
  theme_minimal()

# Step 3: English Plot
p2 <- ggplot(data = exam_data, aes(x = ENGLISH)) +
  geom_dotplot_interactive(
    aes(data_id = ID, tooltip = tooltip_text),
    stackgroups = TRUE,
    binwidth = 1,
    method = "histodot",
    fill = "#2ca02c",
    color = "white",
    alpha = 0.85
  ) +
  coord_cartesian(xlim = c(0, 100)) +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(title = "English Score", x = "ENGLISH") +
  theme_minimal()

# Step 4: Science Plot
p3 <- ggplot(data = exam_data, aes(x = SCIENCE)) +
  geom_dotplot_interactive(
    aes(data_id = ID, tooltip = tooltip_text),
    stackgroups = TRUE,
    binwidth = 1,
    method = "histodot",
    fill = "#ff7f0e",
    color = "white",
    alpha = 0.85
  ) +
  coord_cartesian(xlim = c(0, 100)) +
  scale_y_continuous(NULL, breaks = NULL) +
  labs(title = "Science Score", x = "SCIENCE") +
  theme_minimal()

# Step 5: Combine the 3 plots vertically
combined_plot <- p1 / p2 / p3

# Step 6: Render as interactive graphic
girafe(
  code = print(combined_plot),
  width_svg = 9,
  height_svg = 5,
  options = list(
    opts_hover(css = "fill: red; stroke: black; stroke-width: 1.5px;"),
    opts_hover_inv(css = "opacity: 0.2;"),
    opts_tooltip(css = "background-color: white; color: black; border: 1px solid #ccc; padding: 6px; border-radius: 5px; font-size: 12px;")
  )
)

##3.7 Interactive Data Visualisation - plotly methods!

plot_ly(data = exam_data, 
        x = ~ENGLISH, 
        y = ~MATHS, 
        color = ~RACE)

try out using plot_ly

exam_data <- exam_data %>%
  mutate(
    TOTAL = MATHS + ENGLISH + SCIENCE,
    tooltip_text = paste0(
      "Class: ", CLASS, "<br>",
      "Maths: ", MATHS, "<br>",
      "English: ", ENGLISH, "<br>",
      "Science: ", SCIENCE, "<br>",
      "Total: ", TOTAL, " / 300"
    )
  )

plot_ly(
  data = exam_data,
  x = ~ENGLISH,
  y = ~MATHS,
  type = "scatter",
  mode = "markers",
  color = ~CLASS,
  colors = "Set2",  # or try "Paired", "Dark2"
  text = ~tooltip_text,
  hoverinfo = "text",
  marker = list(
    size = 10,
    opacity = 0.7,
    line = list(width = 1, color = "#000000")
  )
) %>%
  layout(
    title = list(text = "English vs Maths by Class", x = 0.05),
    xaxis = list(title = "English Score", range = c(0, 100)),
    yaxis = list(title = "Maths Score", range = c(0, 100)),
    legend = list(title = list(text = "<b>Class</b>"))
  )

##3.8 Interactive Data Visualisation - crosstalk methods!

DT::datatable(exam_data, class= "compact")

###Try to make the table fit

d <- highlight_key(exam_data)

# 1. Interactive ggplotly plot
p <- ggplot(d, aes(ENGLISH, MATHS)) +
  geom_point(size = 2, alpha = 0.7) +
  coord_cartesian(xlim = c(0, 100), ylim = c(0, 100)) +
  theme_minimal()

gg <- highlight(ggplotly(p), "plotly_selected")

# 2. Enhanced datatable
data_table <- DT::datatable(
  d,
  options = list(
    pageLength = 20,
    scrollY = "600px",
    autoWidth = TRUE,
    dom = 'Bfrtip'
  ),
  class = "stripe hover compact",
  rownames = FALSE
)

# 3. Arrange with wider proportions
crosstalk::bscols(
  widths = c(5, 7),  # Give plot more breathing room
  gg,
  data_table
)

#4 Programming Animated Statistical Graphics with R

pacman::p_load(readxl, gifski, gapminder,
               plotly, gganimate, tidyverse)

##import

col <- c("Country", "Continent")
globalPop <- read_xls("chap03/data/GlobalPopulation.xls",
                      sheet="Data") %>%
  mutate(across(col, as.factor)) %>%
  mutate(Year = as.integer(Year))

##Animated Data Visualisation: gganimate methods

###Static bubble plot

ggplot(globalPop, aes(x = Old, y = Young, 
                      size = Population, 
                      colour = Country)) +
  geom_point(alpha = 0.7, 
             show.legend = FALSE) +
  scale_colour_manual(values = country_colors) +
  scale_size(range = c(2, 12)) +
  labs(title = 'Year: {frame_time}', 
       x = '% Aged', 
       y = '% Young') 

###Animated bubble plot

ggplot(globalPop, aes(x = Old, y = Young, 
                      size = Population, 
                      colour = Country)) +
  geom_point(alpha = 0.7, 
             show.legend = FALSE) +
  scale_colour_manual(values = country_colors) +
  scale_size(range = c(2, 12)) +
  labs(title = 'Year: {frame_time}', 
       x = '% Aged', 
       y = '% Young') +
  transition_time(Year) +       
  ease_aes('linear')          

##4.4 Animated Data Visualisation: plotly

###4.4.1 Building an animated bubble plot: ggplotly() method

gg <- ggplot(globalPop, 
       aes(x = Old, 
           y = Young, 
           size = Population, 
           colour = Country)) +
  geom_point(aes(size = Population,
                 frame = Year),
             alpha = 0.7) +
  scale_colour_manual(values = country_colors) +
  scale_size(range = c(2, 12)) +
  labs(x = '% Aged', 
       y = '% Young') + 
  theme(legend.position='none')

ggplotly(gg)

###4.4.2 Building an animated bubble plot: plot_ly() method

bp <- suppressWarnings({
  globalPop %>%
    plot_ly(
      x = ~Old, 
      y = ~Young, 
      size = ~Population, 
      color = ~Continent,
      sizes = c(2, 100),
      frame = ~Year, 
      text = ~Country, 
      hoverinfo = "text",
      type = 'scatter',
      mode = 'markers'
    ) %>%
    layout(showlegend = FALSE)
})
bp